/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.tdb2.store;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import org.apache.jena.query.*;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFDataMgr;
import org.apache.jena.sparql.core.Quad;
import org.apache.jena.sparql.sse.SSE;
import org.apache.jena.sys.JenaSystem;
import org.apache.jena.tdb2.TDB2;
import org.apache.jena.tdb2.junit.TL;

/** Tests of datasets, prefixes, special URIs etc (see also {@link org.apache.jena.sparql.graph.GraphsTests} */
public class TestDatasetTDB
{
    static {
        JenaSystem.init();
    }

    private Dataset dataset;

    @BeforeEach public void before() {
        dataset = TL.createTestDatasetMem();
        dataset.begin(ReadWrite.WRITE);
    }

    @AfterEach public void after() {
        dataset.abort();
        dataset.end();
        TL.expel(dataset);

    }

    private Dataset dataset() {
        return dataset;
    }

    private static void load(Model model, String file)
    {
        RDFDataMgr.read(model, file);
    }

    private static String base1 = "http://example/";
    private static String baseNS = "http://example/ns#";

    private static void load1(Model model)
    {
        model.setNsPrefix("", base1);
        Resource r1 = model.createResource(base1+"r1");
        Property p1 = model.createProperty(baseNS+"p1");
        model.add(r1, p1, "x1");
        model.add(r1, p1, "x2");
    }

    private static void load2(Model model)
    {
        Resource r2 = model.createResource(base1+"r2");
        Property p1 = model.createProperty(baseNS+"p1");
        model.add(r2, p1, "x1");
        model.add(r2, p1, "x2");
    }

    private static void load3(Model model)
    {
        Resource r3 = model.createResource(base1+"r3");
        Property p1 = model.createProperty(baseNS+"p2");
        model.add(r3, p1, "x1");
        model.add(r3, p1, "x2");
    }

    @Test public void prefix1()
    {
        Dataset ds = dataset();
        Model m = ds.getDefaultModel();
        load1(m);
        String x = m.expandPrefix(":x");
        assertEquals(x, base1+"x");
    }

    @Test public void prefix2()
    {
        Dataset ds = dataset();
        Model m = ds.getDefaultModel();
        load1(m);
        Model m2 = ds.getNamedModel("http://example/graph/");
        String x = m2.expandPrefix(":x");
        // Shared prefixes.
        assertEquals("http://example/x", x);
    }

    @Test public void query1()
    {
        Dataset ds = dataset();
        Model expected = ds.getDefaultModel();
        load1(expected);

        String qs = "CONSTRUCT {?s ?p ?o } WHERE {?s ?p ?o}";
        Query q = QueryFactory.create(qs);
        QueryExecution qExec = QueryExecutionFactory.create(q, ds);
        Model m2 = qExec.execConstruct();
        assertTrue(expected.isIsomorphicWith(m2));
    }

    @Test public void query2()
    {
        Dataset ds = dataset();
        Model m = ds.getDefaultModel();
        load1(m);

        String qs = "CONSTRUCT {?s ?p ?o } WHERE { GRAPH <http://example/graph/> {?s ?p ?o}}";
        Query q = QueryFactory.create(qs);
        QueryExecution qExec = QueryExecutionFactory.create(q, ds);
        Model m2 = qExec.execConstruct();
        assertTrue(m2.isEmpty());
    }

    static String defaultGraph = Quad.defaultGraphIRI.getURI();
    static String unionGraph = Quad.unionGraph.getURI();

    @Test public void special1()
    {
        Dataset ds = dataset();
        Model expected = ds.getDefaultModel();
        load1(expected);

        String qs = "CONSTRUCT { ?s ?p ?o } WHERE { GRAPH <"+defaultGraph+"> {?s ?p ?o}}";
        Query q = QueryFactory.create(qs);
        QueryExecution qExec = QueryExecutionFactory.create(q, ds);
        Model m2 = qExec.execConstruct();
        assertTrue(expected.isIsomorphicWith(m2));
    }

    @Test public void special2()
    {
        Dataset ds = dataset();

        load1(ds.getDefaultModel());
        load2(ds.getNamedModel("http://example/graph1"));
        load3(ds.getNamedModel("http://example/graph2"));

        Model expected = ModelFactory.createDefaultModel();
        load2(expected);
        load3(expected);

        String qs = "CONSTRUCT { ?s ?p ?o } WHERE { GRAPH <"+unionGraph+"> {?s ?p ?o}}";
        Query q = QueryFactory.create(qs);
        QueryExecution qExec = QueryExecutionFactory.create(q, ds);
        Model m2 = qExec.execConstruct();
        assertTrue(expected.isIsomorphicWith(m2));
    }

    @Test public void special3()
    {
        JenaSystem.init();
        Dataset ds = dataset();

        load1(ds.getDefaultModel());
        load2(ds.getNamedModel("http://example/graph1"));
        load3(ds.getNamedModel("http://example/graph2"));

        Model expected = ModelFactory.createDefaultModel();
        load2(expected);
        load3(expected);

        String qs = "CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }";
        Query q = QueryFactory.create(qs);
        QueryExecution qExec = QueryExecutionFactory.create(q, ds);
        qExec.getContext().set(TDB2.symUnionDefaultGraph, true);
        Model m2 = qExec.execConstruct();
        if ( ! expected.isIsomorphicWith(m2) )
        {
            System.out.println("---- ----");
            SSE.write(ds.asDatasetGraph());
            System.out.println("-- Expected");
            expected.write(System.out, "TTL");
            System.out.println("-- Actual");
            m2.write(System.out, "TTL");
            System.out.println("---- ----");

        }
        assertTrue(expected.isIsomorphicWith(m2));
    }

    @Test public void special4()
    {
        Dataset ds = dataset();

        load1(ds.getDefaultModel());
        load2(ds.getNamedModel("http://example/graph1"));
        load3(ds.getNamedModel("http://example/graph2"));

        Model expected = ModelFactory.createDefaultModel();
        load2(expected);
        load3(expected);

        String qs = "PREFIX : <"+baseNS+"> SELECT (COUNT(?x) as ?c) WHERE { ?x (:p1|:p2) 'x1' }";
        Query q = QueryFactory.create(qs);

        long c_m;
        // Model
        try (QueryExecution qExec = QueryExecutionFactory.create(q, expected)) {
            c_m = qExec.execSelect().next().getLiteral("c").getLong();
        }

        // dataset
        long c_ds;
        try (QueryExecution qExec = QueryExecutionFactory.create(q, ds)) {
            qExec.getContext().set(TDB2.symUnionDefaultGraph, true);
            c_ds = qExec.execSelect().next().getLiteral("c").getLong();
        }

//        String qs2 = "PREFIX : <"+baseNS+"> SELECT * WHERE { ?x (:p1|:p2) 'x1' }";
//        Query q2 = QueryFactory.create(qs2);
//        qExec = QueryExecutionFactory.create(q2, ds);
//        qExec.getContext().set(TDB.symUnionDefaultGraph, true);
//        ResultSetFormatter.out(qExec.execSelect());
//
//        qExec = QueryExecutionFactory.create(q2, m);
//        ResultSetFormatter.out(qExec.execSelect());
        // --------

        assertEquals(c_m, c_ds);
    }

    @Test public void special5()
    {
        Dataset ds = dataset();

        //load1(ds.getDefaultModel());
        load1(ds.getNamedModel("http://example/graph1"));  // Same triples, different graph
        load1(ds.getNamedModel("http://example/graph2"));

        Model m = ds.getNamedModel(unionGraph);
        assertEquals(2, m.size());
    }

    // Put a model into a general dataset and use it.
    @Test public void generalDataset1()
    {
        Dataset ds = dataset();
        load1(ds.getDefaultModel());
        load2(ds.getNamedModel("http://example/graph1"));
        load3(ds.getNamedModel("http://example/graph2"));
        Model expected = ds.getNamedModel("http://example/graph2");

        // Use graph2 as default model.
        Dataset ds2 = DatasetFactory.create();
        ds2.setDefaultModel(ds.getNamedModel("http://example/graph2"));

        String qs = "CONSTRUCT {?s ?p ?o } WHERE { ?s ?p ?o}";
        Query q = QueryFactory.create(qs);
        QueryExecution qExec = QueryExecutionFactory.create(q, ds2);
        Model m2 = qExec.execConstruct();
        assertTrue(expected.isIsomorphicWith(m2));
    }

    @Test public void generalDataset2()
    {
        Dataset ds = dataset();
        load1(ds.getDefaultModel());
        load2(ds.getNamedModel("http://example/graph1"));
        load3(ds.getNamedModel("http://example/graph2"));
        Model m = ds.getNamedModel("http://example/graph2");

        // Use graph1 as a differently named model.
        Dataset ds2 = DatasetFactory.create();
        ds2.addNamedModel("http://example/graphOther", m);

        String qs = "CONSTRUCT {?s ?p ?o } WHERE { {?s ?p ?o} UNION { GRAPH <http://example/graphOther> {?s ?p ?o} } }";
        Query q = QueryFactory.create(qs);
        QueryExecution qExec = QueryExecutionFactory.create(q, ds2);
        Model m2 = qExec.execConstruct();
        if ( ! m.isIsomorphicWith(m2) )
        {
            System.out.println(m.getGraph().getClass().getSimpleName()+"/"+m.size()+" : "+m2.getGraph().getClass().getSimpleName()+"/"+m2.size());
            System.out.println("---- Different:" );
            RDFDataMgr.write(System.out, m, Lang.TTL);
            System.out.println("---- ----" );
            RDFDataMgr.write(System.out, m2, Lang.TTL);
            System.out.println("---- ----");
        }

        assertTrue(m.isIsomorphicWith(m2));
    }

    @Test public void generalDataset3()
    {
        Dataset ds = dataset();
        load1(ds.getDefaultModel());
        load2(ds.getNamedModel("http://example/graph1"));
        load3(ds.getNamedModel("http://example/graph2"));
        Model m = ds.getDefaultModel();

        // Use the default model in one dataset as a named model in another.
        Dataset ds2 = DatasetFactory.create();
        ds2.addNamedModel("http://example/graphOther", m);

        String qs = "CONSTRUCT {?s ?p ?o } WHERE { {?s ?p ?o} UNION { GRAPH <http://example/graphOther> {?s ?p ?o} } }";
        Query q = QueryFactory.create(qs);
        QueryExecution qExec = QueryExecutionFactory.create(q, ds2);
        Model m2 = qExec.execConstruct();
        assertTrue(m.isIsomorphicWith(m2));
    }

    // removeAll
}
